package org.meetunexpectedly.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Description: redis缓存工具
 * @Author: pupil
 * @Date: 2024/08/05 下午 6:47
 */
@Service(value = "redisCacheUtil")
public class RedisCacheUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false失败
     */
    public boolean set(String key, Object value, long time) {

        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存
     *
     * @param key 键
     * @return
     */
    public Set sGet(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 获取set的数据量
     *
     * @param key
     * @return
     */
    public Long getSetSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }


    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }


    /**
     * 删除缓存
     *
     * @param key key 可以传一个值 或多个
     * @return true成功 false失败
     */
    public boolean del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(Arrays.asList(key));
            }
            return true;
        }
        return false;
    }

    /**
     * 根据健值查询所有值,并根据分数从高到低排序
     *
     * @param key 键
     * @return
     */
    public Set zGet(String key) {
        return redisTemplate.opsForZSet().reverseRange(key, 0, -1);
    }


    /**
     * 设置键值并把创建时间设为分数
     *
     * @param key   键
     * @param value 值
     * @param time  创建时间
     * @return
     */
    public boolean zSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForZSet().add(key, value, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据健值和分页信息查询值,并根据分数从高到低排序
     *
     * @param key      键
     * @param pageNum  页数
     * @param pageSize 页面数据量
     * @return
     */
    public Set<ZSetOperations.TypedTuple<Object>> zSetGetByPage(String key, long pageNum, long pageSize) {
        try {
            // 判断redis中是否存在该key
            if (redisTemplate.hasKey(key)) {
                long start = (pageNum - 1) * pageSize;
                long end = pageNum * pageSize - 1;
                Long size = redisTemplate.opsForZSet().size(key);
                if (end > size) {
                    end = -1;
                }
                return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
            } else {
                return null;
            }

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 集合缓存添加并设置创建时间
     *
     * @param key   键
     * @param value 值
     * @param time  创建时间
     */
    public void addZSet(String key, Object value, long time) {
        redisTemplate.opsForZSet().add(key, value, time);
    }

    /**
     * 集合缓存中删除对应的值
     *
     * @param key    键
     * @param values 值
     * @return
     */
    public void removeZSet(String key, Object... values) {
        redisTemplate.opsForZSet().remove(key, values);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hsSet(String key, Map<Object, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hsGet(String key) {
        return redisTemplate.opsForHash().entries(key);
    }


    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public Long increment(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }


    /**
     * @param key
     * @param score 排序分数
     * @param val   值
     * @param time  失效时间
     */
    public void zAdd(String key, double score, Object val, long time) {
        redisTemplate.opsForZSet().add(key, val, score);
        // 设置失效时间
        this.expire(key, time);
    }

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 失效时间(分钟)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            // 时间大于0时，设置失效时间
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long removeCount = redisTemplate.opsForSet().remove(key, values);
            return removeCount;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 根据key随机从集合中获取一个值
     *
     * @param key 键
     * @return
     */
    public Object sRandom(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }

    /**
     * 根据key随机从集合中获取一个值
     * redis管道执行命令
     *
     * @param keys 键集合
     * @return list集合
     */
    public List<Object> sRandom(List<String> keys) {
        List<Object> list = redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                for (String key : keys) {
                    connection.sRandMember(key.getBytes());
                }
                return null;
            }
        });

        // 有可能为空
        List result = new ArrayList();
        for (Object o : list) {
            if (o != null) {
                result.add(o);
            }
        }
        return result;
    }

    /**
     * 值不存储时存储成功
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean sAdd(String key, Object value, long time) {
        return redisTemplate.opsForZSet().addIfAbsent(key, value, time);
    }

    /**
     * 接收一个管道
     *
     * @param redisCallback 管道
     * @return
     */
    public List pipeline(RedisCallback redisCallback) {
        return redisTemplate.executePipelined(redisCallback);
    }


    /**
     * 判断key是否存在
     *
     * @param key
     * @return
     */
    public boolean isKeyExists(String key) {
        return redisTemplate.hasKey(key);
    }
}
